/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <journal/common/performance_service.h>
#include <journal/app/constants.h>

namespace journal {

const b::float32 performance_service::_margin = 4.f;
const b::float32 performance_service::_graphicHeight = 40.f;
const b::float32 performance_service::_graphicWidth = 420.f;
const b::float32 performance_service::_labelHeight = 20.f;
const b::uint32 performance_service::_backdropAlpha = 196;

performance_service::data::data(): 
    buffer(constants::performance::sampleCount()), 
    lastResetMs(0.0), 
    dirty(false), 
    lastNonZero(0), 
    lastNonZeroAvg(0), 
    init(false) 
{}

void performance_service::init(const std::shared_ptr<g::iengine>& graphics)
{
    g::viewport vp;
    graphics->getViewport(&vp);
    a::resize_args args;
    args.width = (b::int32)vp.width;
    args.height = (b::int32)vp.height;
    onResize(args);

    _sw.reset();
    _lastResetMs = _sw.getElapsedMilliseconds();

    // reset all performance counters to start collecting from now
    this->resetAll();

    // initialize all event descriptions
    _desc[static_cast<int>(performance_event::renderFrame)] = performance_event_desc(true, g::packColor(198, 200, 0, 0), L"frame rate", L"performance\\monitors\\frame rate", true);
    _desc[static_cast<int>(performance_event::renderStroke)] = performance_event_desc(true, g::packColor(198, 0, 200, 0), L"ink rendering",L"performance\\monitors\\ink rendering", false);
    _desc[static_cast<int>(performance_event::inkSmoothing)] = performance_event_desc(true, g::packColor(198, 200, 200, 0), L"ink smoothing",L"performance\\monitors\\ink smoothing", true);
    _desc[static_cast<int>(performance_event::mipMapping)] = performance_event_desc(true, g::packColor(198, 0, 200, 200), L"mip map gen",L"performance\\monitors\\mip map generation", false);
    _desc[static_cast<int>(performance_event::input)] = performance_event_desc(true, g::packColor(198, 200, 0, 200), L"input",L"performance\\monitors\\input", true);
    _desc[static_cast<int>(performance_event::modelLoad)] = performance_event_desc(true, g::packColor(255, 255, 0, 200), L"model load",L"performance\\monitors\\model load", true);
    _desc[static_cast<int>(performance_event::modelSave)] = performance_event_desc(true, g::packColor(255, 255, 200, 200), L"model save",L"performance\\monitors\\model save", true);
    _desc[static_cast<int>(performance_event::normalCalculation)] = performance_event_desc(true, g::packColor(198, 100, 200, 0), L"normal calculation",L"performance\\monitors\\normal calculation", true);
    _desc[static_cast<int>(performance_event::deferredRender)] = performance_event_desc(true, g::packColor(198, 200, 100, 0), L"deferred rendering",L"performance\\monitors\\deferred rendering", true);

    // initialize var console settings
    constants::settings()->registerBool(-1, _desc[static_cast<int>(performance_event::renderFrame)].setting, _desc[static_cast<int>(performance_event::renderFrame)].enabled);
    constants::settings()->registerBool(-1, _desc[static_cast<int>(performance_event::renderStroke)].setting, _desc[static_cast<int>(performance_event::renderStroke)].enabled);
    constants::settings()->registerBool(-1, _desc[static_cast<int>(performance_event::inkSmoothing)].setting, _desc[static_cast<int>(performance_event::inkSmoothing)].enabled);
    constants::settings()->registerBool(-1, _desc[static_cast<int>(performance_event::mipMapping)].setting, _desc[static_cast<int>(performance_event::mipMapping)].enabled);
    constants::settings()->registerBool(-1, _desc[static_cast<int>(performance_event::input)].setting, _desc[static_cast<int>(performance_event::input)].enabled);
    constants::settings()->registerBool(-1, _desc[static_cast<int>(performance_event::modelLoad)].setting, _desc[static_cast<int>(performance_event::modelLoad)].enabled);
    constants::settings()->registerBool(-1, _desc[static_cast<int>(performance_event::modelSave)].setting, _desc[static_cast<int>(performance_event::modelSave)].enabled);
    constants::settings()->registerBool(-1, _desc[static_cast<int>(performance_event::normalCalculation)].setting, _desc[static_cast<int>(performance_event::normalCalculation)].enabled);
    constants::settings()->registerBool(-1, _desc[static_cast<int>(performance_event::deferredRender)].setting, _desc[static_cast<int>(performance_event::deferredRender)].enabled);

    _settingMap[_desc[static_cast<int>(performance_event::renderFrame)].setting] = performance_event::renderFrame;
    _settingMap[_desc[static_cast<int>(performance_event::renderStroke)].setting] = performance_event::renderStroke;
    _settingMap[_desc[static_cast<int>(performance_event::inkSmoothing)].setting] = performance_event::inkSmoothing;
    _settingMap[_desc[static_cast<int>(performance_event::mipMapping)].setting] = performance_event::mipMapping;
    _settingMap[_desc[static_cast<int>(performance_event::input)].setting] = performance_event::input;
    _settingMap[_desc[static_cast<int>(performance_event::modelLoad)].setting] = performance_event::modelLoad;
    _settingMap[_desc[static_cast<int>(performance_event::modelSave)].setting] = performance_event::modelSave;
    _settingMap[_desc[static_cast<int>(performance_event::normalCalculation)].setting] = performance_event::normalCalculation;
    _settingMap[_desc[static_cast<int>(performance_event::deferredRender)].setting] = performance_event::deferredRender;

    // observe settings
    std::shared_ptr<performance_service> t = shared_from_this();
    std::shared_ptr<isettings_service_observer> o = std::dynamic_pointer_cast<isettings_service_observer>(t);

    _settingsObserverCookie = constants::settings()->addObserver(
        std::dynamic_pointer_cast<isettings_service_observer>(shared_from_this()));
}

std::shared_ptr<g::ifont> performance_service::getFont()
{
    _font = constants::graphics()->getAssetManager()->getOrCreateFont(
        constants::graphics(), 
        constants::references::fontName(), 
        constants::references::fontSizeSmall(),
        false // right to left
        );

    return _font;
}

void performance_service::collect(const performance_event e, const double duration)
{
    data& d = _data[static_cast<int>(e)];
    if (!d.init)
    {
        // initialize it the first time around
        reset(e);
        d.init = true;
    }

    BAJA_LOCK(d._mutex);
    BAJA_VERIFY(d.lastResetMs > 0);

    d.dirty = true;
    d.buffer.current()->value += duration;
    d.buffer.current()->count++;

    if (duration != 0) d.lastNonZero = duration;
}
    
void performance_service::reset(const performance_event e, bool restart)
{
    data& d = _data[static_cast<int>(e)];
    BAJA_LOCK(d._mutex);

    if (restart) d.buffer.reset();
    d.buffer.add();
    d.buffer.current()->count = 0;
    d.buffer.current()->value = 0.0;
    d.lastResetMs = _sw.getElapsedMilliseconds();
    d.dirty = false;
}

void performance_service::calculatePositions(
    const int slot,
    const performance_event e,
    const b::wchar* topLabel, 
    const b::wchar* bottomLabel, 
    const b::float32 previousTop,
    m::rectangle* topLabelRect,
    m::rectangle* bottomLabelRect,
    m::rectangle* graphicRect,
    m::rectangle* totalRect
    )
{
    b::float32 graphicHeight = _desc[(int)e].showGraph ? _graphicHeight : 0;
    b::float32 graphicMargins = _desc[(int)e].showGraph ? _margin*2 : 0;

    b::float32 slotTop = previousTop - (graphicHeight + _labelHeight*2 + _margin*2 + graphicMargins);

    b::float32 slotLeft = _graphicWidth + _margin*2;
    slotLeft = _vp.right - slotLeft;

    totalRect->top = slotTop;
    totalRect->left = slotLeft;
    totalRect->bottom = slotTop + (graphicHeight + _labelHeight*2 + _margin*2 + graphicMargins);
    totalRect->right = slotLeft + _graphicWidth + _margin*2;

    // graphic
    graphicRect->top = slotTop + _labelHeight + _margin *2;
    graphicRect->left = slotLeft + _margin;
    graphicRect->bottom = graphicRect->top + graphicHeight;
    graphicRect->right = totalRect->right - _margin;

    // top label
    this->getFont()->measureText(
        topLabel,
        g::horizontal_alignment::left,
        g::vertical_alignment::top,
        false,
        topLabelRect
        );

    topLabelRect->left = slotLeft + _margin;
    topLabelRect->top = totalRect->top + _margin;
    topLabelRect->right += topLabelRect->left;
    topLabelRect->bottom += bottomLabelRect->top;

    // bottom label
    this->getFont()->measureText(
        bottomLabel,
        g::horizontal_alignment::left,
        g::vertical_alignment::top,
        false,
        bottomLabelRect
        );

    bottomLabelRect->left = slotLeft + _margin;
    bottomLabelRect->top = totalRect->bottom - _margin - _labelHeight;;
    bottomLabelRect->right += bottomLabelRect->left;
    bottomLabelRect->bottom += bottomLabelRect->top;
}

void performance_service::onResize(const a::resize_args& args)
{
    _vp.left = 0;
    _vp.top = 0;
    _vp.right = (b::float32)args.width;
    _vp.bottom = (b::float32)args.height;
}

void performance_service::resetAll(bool restart)
{
    _lastResetMs = _sw.getElapsedMilliseconds();
    reset(performance_event::renderFrame, restart);
    reset(performance_event::renderStroke, restart);
    reset(performance_event::inkSmoothing, restart);
    reset(performance_event::mipMapping, restart);
    reset(performance_event::input, restart);
    reset(performance_event::modelLoad, restart);
    reset(performance_event::modelSave, restart);
    reset(performance_event::normalCalculation, restart);
    reset(performance_event::deferredRender, restart);
}

void performance_service::onRender()
{		
    if (_firstRender)
    {
        this->resetAll(true);
        _firstRender = false;
    }

    double now = _sw.getElapsedMilliseconds();
    if (now - _lastResetMs >= 1000.0)
    {
        this->resetAll();
    }

    if (!constants::performance::showMonitors())
    {
        return;
    }

    int slot = 1;
    b::float32 previousTop = _vp.bottom - 150; // leave some space for axes

    if (_desc[(int)performance_event::renderStroke].enabled)
    {
        renderSlot(slot++, performance_event::renderStroke, previousTop);
    }

    if (_desc[(int)performance_event::inkSmoothing].enabled)
    {
        renderSlot(slot++, performance_event::inkSmoothing, previousTop);
    }

    if (_desc[(int)performance_event::normalCalculation].enabled)
    {
        renderSlot(slot++, performance_event::normalCalculation, previousTop);
    }

    if (_desc[(int)performance_event::deferredRender].enabled)
    {
        renderSlot(slot++, performance_event::deferredRender, previousTop);
    }

    if (_desc[(int)performance_event::mipMapping].enabled)
    {
        renderSlot(slot++, performance_event::mipMapping, previousTop);
    }

    if (_desc[(int)performance_event::input].enabled)
    {
        renderSlot(slot++, performance_event::input, previousTop);
    }

    if (_desc[(int)performance_event::renderFrame].enabled)
    {
        renderSlot(slot++, performance_event::renderFrame, previousTop);
    }

    if (_desc[(int)performance_event::modelLoad].enabled)
    {
        renderSlot(slot++, performance_event::modelLoad, previousTop);
    }

    if (_desc[(int)performance_event::modelSave].enabled)
    {
        renderSlot(slot++, performance_event::modelSave, previousTop);
    }
}

void performance_service::get(
    const performance_event e,
    b::float32* count,
    b::float32* value
    )
{
    data& d = _data[static_cast<int>(e)];
    BAJA_LOCK(d._mutex);

    if (d.buffer.length() <= 1)
    {
        *count = *value = -1.f;
    }
    else
    {
        *count = static_cast<b::float32>(d.buffer.previous()->count);
        *value = static_cast<b::float32>(d.buffer.previous()->value);
    }
}

void performance_service::renderSlot(
    const int slot, 
    const performance_event e,
    b::float32& previousTop
    )
{
    std::shared_ptr<g::iengine> graphics = constants::graphics();

    m::rectangle topLabelRect;
    m::rectangle bottomLabelRect;
    m::rectangle graphicRect;
    m::rectangle totalRect;

    boost::wformat w;

    w = boost::wformat(L"%s (in ms.)") % _desc[(int)e].label;
    std::wstring slotName = w.str();

    data& d = _data[static_cast<int>(e)];
    BAJA_LOCK(d._mutex);

    std::wstring s;
    if (d.buffer.length() <= 1)
    {
        s = L"---";
    }
    else
    {
        size_t c = d.buffer.previous()->count;
        double v = d.buffer.previous()->value;
        double last = d.lastNonZero;
        double lastAvg = d.lastNonZeroAvg;
        if (c > 0)
        {
            w = boost::wformat(L"x/s: %i, ms/x avg: %0.4f (last: %0.4f), cur x: %0.4f") % c % (v/c) % lastAvg %  last;
            d.lastNonZeroAvg = (v/c);
        }
        else
        {
            w = boost::wformat(L"x/s: %i, ms/x avg: -- (last: %0.4f), cur x: %0.5f") % c % lastAvg % last;
        }

        s = w.str();
    }

    this->calculatePositions(
        slot, 
        e,
        slotName.c_str(),
        s.c_str(),
        previousTop,
        &topLabelRect, 
        &bottomLabelRect, 
        &graphicRect, 
        &totalRect
        );

    // draw total backdrop rectangle
    std::shared_ptr<g::ieffect2> effect;
    effect = graphics->getStandardEffect(g::standard_effect::screenColored);

    effect->begin(graphics);
    graphics->setAlphaBlend(g::enabled_state::enabled);
    graphics->setZBuffer(g::enabled_state::disabled);
    graphics->setCulling(g::cull_state::none);

    graphics->beginScreenSpace(g::primitive_type::triangleStrip);

    graphics->setColor(g::packColor(performance_service::_backdropAlpha, 0, 0, 0));

    m::point4d pos1(totalRect.left, totalRect.top, 1.0f);
    graphics->setScreenSpaceVertex(pos1);

    m::point4d pos2(totalRect.right-1, totalRect.top, 1.0f);
    graphics->setScreenSpaceVertex(pos2);

    graphics->setColor(g::packColor(performance_service::_backdropAlpha, 0, 0, 0));

    m::point4d pos4(totalRect.left, totalRect.bottom-1, 1.0f);
    graphics->setScreenSpaceVertex(pos4);

    m::point4d pos3(totalRect.right-1, totalRect.bottom-1, 1.0f);
    graphics->setScreenSpaceVertex(pos3);

    graphics->end();

    effect->end(graphics);

    // draw the top label
    static const g::packed_color textColor = g::packColor(255,198,198,198);
    this->getFont()->renderText(
        graphics,
        slotName.c_str(),
        textColor,
        g::horizontal_alignment::left,
        g::vertical_alignment::top,
        &topLabelRect
        );

    // draw the bottom label
    this->getFont()->renderText(
        graphics,
        s.c_str(),
        textColor,
        g::horizontal_alignment::left,
        g::vertical_alignment::top,
        &bottomLabelRect
        );

    if (_desc[(int)e].showGraph)
    {
        renderGraphic(slot, e, &graphicRect);
    }

    previousTop = totalRect.top - 5; // add some margin
}

void performance_service::renderGraphic(const int slot, const performance_event e, const m::rectangle* rect)
{
    std::shared_ptr<g::iengine> graphics = constants::graphics();

    // render axes
    graphics->setAlphaBlend(g::enabled_state::enabled);
    graphics->setZBuffer(g::enabled_state::disabled);
    graphics->setCulling(g::cull_state::none);

    graphics->beginScreenSpace(g::primitive_type::lineStrip);

    g::packed_color color = g::packColor(255,198,198,198);

    graphics->setColor(color);

    m::point4d pos1(rect->left, rect->top, 1.0f);
    graphics->setScreenSpaceVertex(pos1);

    m::point4d pos2(rect->left, rect->bottom, 1.0f);
    graphics->setScreenSpaceVertex(pos2);

    m::point4d pos3(rect->right, rect->bottom, 1.0f);
    graphics->setScreenSpaceVertex(pos3);

    graphics->end();

    // render graph
    // first find max
    data& d = _data[static_cast<int>(e)];
    if (d.buffer.length() <= 1) { return; }

    double maxval = -FLT_MAX;
    for (int i = 0; i < d.buffer.length(); i++)
    {
        data_point* cur = d.buffer.at(i);
        if (cur->value > maxval) maxval = cur->value;
    }

    graphics->beginScreenSpace(g::primitive_type::triangleStrip);

    g::packed_color c = g::packColor(
        64,
        g::unpackColor_R(_desc[(int)e].color),
        g::unpackColor_G(_desc[(int)e].color),
        g::unpackColor_B(_desc[(int)e].color)
        );

    graphics->setColor(c);

    for (int i = 0; i < d.buffer.length() - 1; i++)
    {
        data_point* cur = d.buffer.at(i);

        m::point4d p = mapValue(slot, cur->value, maxval, i, rect);
        graphics->setScreenSpaceVertex(p);
        p.y = rect->bottom;
        graphics->setScreenSpaceVertex(p);
    }

    graphics->end();

    graphics->beginScreenSpace(g::primitive_type::lineStrip);

    c = g::packColor(
        255,
        g::unpackColor_R(_desc[(int)e].color),
        g::unpackColor_G(_desc[(int)e].color),
        g::unpackColor_B(_desc[(int)e].color)
        );

    graphics->setColor(c);

    for (int i = 0; i < d.buffer.length() - 1; i++)
    {
        data_point* cur = d.buffer.at(i);

        graphics->setScreenSpaceVertex(mapValue(slot, cur->value, maxval, i, rect));
    }

    graphics->end();
}

m::point4d performance_service::mapValue(int slot, double value, double max, int i, const m::rectangle* rect)
{
    m::point4d p;

    // normalize
    float f = static_cast<float>(value / max);
    f *= _graphicHeight;

    p.y = rect->bottom - f;
    p.x = rect->left + (_graphicWidth / constants::performance::sampleCount()) * i;
    p.z = 1.f;

    return p;
}

void performance_service::onBeforeSettingChange(const b::wchar* fullName)
{
}

void performance_service::onAfterSettingChange(const b::wchar* fullName)
{
    if (std::wstring(fullName).compare(constants::performance::name::reset) == 0)
    {
        this->resetAll();
        unsigned int eventCount = static_cast<unsigned int>(performance_event::_count);
        for (unsigned int i = 0 ; i < eventCount ; ++i)
        {
            data& d = _data[static_cast<int>(i)];
            d.reset();
        }

        return;
    }

    auto it = _settingMap.find(fullName);
    if (it == _settingMap.end())
    {
        return;
    }

    performance_event e = it->second;
    performance_event_desc& desc = _desc[(int)e];
    data& d = _data[static_cast<int>(e)];
    BAJA_LOCK(d._mutex);

    desc.enabled = *(static_cast<bool*>(constants::settings()->findSetting(fullName)->getValueBuffer()));
}

} // namespace journal
